package org.zturn.exhibition.util;

import cn.hutool.core.codec.Base64;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;

/**
 * @ClassName : AESUtil
 * @Description : AES加密工具类
 * @Author : AD
 */
public class AESUtil {

    public static final String CHAR_ENCODING = "UTF-8";

    /**
     * [常见算法]AES、DES、RSA、Blowfish、RC4 等等
     * [常见的模式] ECB (电子密码本模式)、CBC (密码分组链接模式)、CTR (计数模式) 等等
     * [常见的填充] NoPadding、PKCS5Padding、PKCS7Padding 等等
     * <p>
     * [AES算法]可以有以下几种常见的值：
     * AES：标准的AES算法。
     * AES/CBC/PKCS5Padding：使用CBC模式和PKCS5填充的AES算法。
     * AES/ECB/PKCS5Padding：使用ECB模式和PKCS5填充的AES算法。
     * AES/GCM/NoPadding：使用GCM模式的AES算法，不需要填充。
     * AES/CCM/NoPadding：使用CCM模式的AES算法，不需要填充。
     * AES/CFB/NoPadding：使用CFB模式的AES算法，不需要填充。
     */
    public static final String
            AES_ALGORITHM = "AES";

    public static char[] HEXCHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    /**
     * Description: 随机生成 AESKey密钥
     *
     * @param length 随机生成密钥长度
     * @return java.lang.String
     */
    public static String getAESKey(int length) throws Exception {
        /*
         * Random类用于生成伪随机数。
         * */
        Random random = new Random();
        StringBuilder ret = new StringBuilder();
        for (int i = 0; i < length; i++) {
            // 选择生成数字还是字符
            boolean isChar = (random.nextInt(2) % 2 == 0);
            /* 0随机生成一个字符*/
            if (isChar) {
                // 选择生成大写字母 / 小写字母
                int choice = (random.nextInt(2) % 2 == 0) ? 65 : 97;
                ret.append((char) (choice + random.nextInt(26)));
                /* 1随机生成一个数字 */
            } else {
                ret.append(random.nextInt(10));
            }
        }
        return ret.toString();
    }

    /**
     * Description: 加密
     *
     * @param data   待加密数据内容
     * @param aesKey 加密密钥
     * @return byte[]
     */
    public static byte[] encrypt(byte[] data, byte[] aesKey) {
        if (aesKey.length != 16) {
            throw new RuntimeException("Invalid AES key length (must be 16 bytes) !");
        }
        try {
            /*
             * 创建一个SecretKeySpec对象来包装AES密钥。
             * 它使用了aesKey字节数组作为密钥，并指定算法为"AES"。
             * 这个对象用来提供对称加密算法的密钥。
             * */
            SecretKeySpec secretKey = new SecretKeySpec(aesKey, "AES");
            /*
             * 获取SecretKeySpec对象中的编码形式，将其存储在encodedFormat字节数组中。
             * 这个编码形式可以被用来重新构造密钥。
             * */
            byte[] encodedFormat = secretKey.getEncoded();
            /*
             * 使用encodedFormat字节数组创建了另一个SecretKeySpec对象secKey。
             * 这个对象也用来提供对称加密算法的密钥。
             * */
            SecretKeySpec secKey = new SecretKeySpec(encodedFormat, "AES");
            /*
             * 使用Cipher类的getInstance()方法获取了一个Cipher对象(创建密码器)。
             * 这个对象用来完成加密或解密的工作。
             * */
            Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
            /*
             * 码调用init()方法来初始化Cipher对象(初始化)。
             * 它要求传入操作模式和提供密钥的对象，这里使用Cipher.ENCRYPT_MODE代表加密模式，以及之前创建的secKey对象作为密钥。
             * */
            cipher.init(Cipher.ENCRYPT_MODE, secKey);
            /*
             * 用Cipher对象对data进行加密操作，得到加密后的结果存储在result字节数组中。
             * */
            byte[] result = cipher.doFinal(data);
            return result;
        } catch (Exception e) {
            throw new RuntimeException(" encrypt fail! ", e);
        }
    }

    /**
     * Description: 解密
     *
     * @param data   解密数据
     * @param aesKey 解密密钥
     * @return byte[]
     */
    public static byte[] decrypt(byte[] data, byte[] aesKey) {
        if (aesKey.length != 16) {
            throw new RuntimeException(" Invalid AES Key length ( must be 16 bytes)");
        }
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES");
            byte[] encodedFormat = secretKeySpec.getEncoded();
            SecretKeySpec secKey = new SecretKeySpec(encodedFormat, "AES");
            /* 创建密码器 */
            Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
            /* 初始化密码器 */
            cipher.init(Cipher.DECRYPT_MODE, secKey);
            byte[] result = cipher.doFinal(data);
            return result;
        } catch (Exception e) {
            throw new RuntimeException(" Decrypt Fail !", e);
        }
    }

    /**
     * Description:加密数据，并转换为Base64编码格式！
     *
     * @param data   待加密数据
     * @param aeskey 加密密钥
     * @return java.lang.String
     */
    public static String

    encryptToBase64(String data, String aeskey) {
        try {
            byte[] valueByte = encrypt(data.getBytes(CHAR_ENCODING), aeskey.getBytes(CHAR_ENCODING));
            /* 加密数据转 Byte[]--> 换为Base64 --> String */
            return Base64.encode(valueByte);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(" Encrypt Fail !", e);
        }
    }

    /**
     * Description: 解密数据，将Basse64格式的加密数据进行解密操作
     *
     * @param data
     * @param aeskey
     * @return java.lang.String
     */
    public static String

    decryptFromBase64(String data, String aeskey) {
        try {
            byte[] originalData = Base64.decode(data.getBytes());
            byte[] valueByte = decrypt(originalData, aeskey.getBytes(CHAR_ENCODING));
            return new String(valueByte, CHAR_ENCODING);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Decrypt Fail !", e);
        }
    }

    /**
     * Description:加密数据，aesKey为Base64格式时，并将加密后的数据转换为Base64编码格式
     *
     * @param data
     * @param aesKey
     * @return java.lang.String
     */
    public static String

    encryptWithKeyBase64(String data, String aesKey) {
        try {
            byte[] valueByte = encrypt(data.getBytes(CHAR_ENCODING), Base64.decode(aesKey.getBytes()));
            return Base64.encode(valueByte);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Encrypt Fail!", e);
        }
    }

    /**
     * Description: 解密数据，数据源为Base64格式，且 aesKey为Base64编码格式
     *
     * @param data
     * @param aesKey
     * @return java.lang.String
     */
    public static String

    decryptWithKeyBase64(String data, String aesKey) {
        try {
            byte[] originalDate = Base64.decode(data.getBytes());
            byte[] valueByte = decrypt(originalDate, Base64.decode(aesKey.getBytes()));
            return new String(valueByte, CHAR_ENCODING);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Decrypt Fail !", e);
        }

    }

    /**
     * Description:通过密钥生成器生成一个随机的 AES 密钥，并将其以字节数组的形式返回。
     * 主要功能是生成并返回一组随机的密钥字节数组，这些字节数组可用于加密和解密数据。
     *
     * @param
     * @return byte[]
     */
    public static byte[] generateRandomAesKey() {
        KeyGenerator keyGenerator = null;
        try {
            /*
             * KeyGenerator是Java Cryptography Architecture（JCA）提供的主要密钥生成器类之一，用于生成对称加密算法的密钥。
             * 获取一个用于生成AES算法密钥的KeyGenerator实例，以便在加密和解密操作中使用该密钥。
             * */
            keyGenerator = KeyGenerator.getInstance(AES_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("GenerateRandomKey Fail ！", e);
        }
        /*
         * SecureRandom 类提供了一种用于生成加密强随机数的实现。
         * */
        SecureRandom secureRandom = new SecureRandom();
        /*
         * 初始化密钥生成器 keyGenerator。
         * 初始化密钥生成器时使用了 SecureRandom 实例，以确保生成的密钥具有足够的随机性。
         * */
        keyGenerator.init(secureRandom);
        /*
         * 调用 generateKey() 方法，使用初始化后的 keyGenerator 生成密钥对象 key。
         * */
        Key key = keyGenerator.generateKey();
        //返回生成的密钥的字节数组表示。
        return key.getEncoded();
    }

    /**
     * Description: 通过密钥生成器生成一个随机的 AES 密钥，并转化为Base64格式
     *
     * @param
     * @return java.lang.String
     */
    public static String

    generateRandomAesKeyWithBase64() {
        return Base64.encode(generateRandomAesKey());
    }

    /* ！！当GET请求进行加密时，地址上的加密参数就以16进制字符串的方式进行传输，否则特殊符号路径无法解析[ +、/、=]等Base64编码格式 */

    /**
     * Description: 从Byte[] 数组转 16进制字符串
     *
     * @param b
     * @return java.lang.String
     */
    public static String

    toHexString(byte[] b) {
        /*
         * 每个字节都可以用两个十六进制字符来表示，因此初始化的容量是字节数组长度的两倍。
         * */
        StringBuilder sb = new StringBuilder(b.length * 2);
        for (int i = 0; i < b.length; i++) {
            /*
             * 首先取字节的高四位，然后查找对应的十六进制字符，并将其追加到StringBuilder中
             * */
            sb.append(HEXCHAR[(b[i] & 0xf0) >>> 4]);
            /*
             * 取字节的低四位，找到对应的十六进制字符，并追加到StringBuilder中。
             * */
            sb.append(HEXCHAR[b[i] & 0x0f]);
        }
        return sb.toString();
    }


    /**
     * Description: 从16进制字符串转 byte[] 数组
     *
     * @param s
     * @return byte[]
     */
    public static final byte[] toBytes(String s) {
        byte[] bytes;
        bytes = new byte[s.length() / 2];
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) Integer.parseInt(s.substring(2 * i, 2 * i + 2), 16);
        }
        return bytes;
    }
}